library flutter_vap;

import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:dio/dio.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';

typedef VapWidgetCreatedCallback = void Function(
    FlutterVapController controller);

typedef OnRenderFrame(int frameIndex, int frameTotal);

typedef OnPreparedCompleted(VapConfig config);

typedef OnMixFrameRender(MixFrameInfo info);

typedef OnResourceClick(String tag);

class VapWidget extends StatefulWidget {
  final VapWidgetCreatedCallback? onPlatformViewCreated;
  final VoidCallback? onComplete;
  final Future? delayToShowFuture;
  final OnRenderFrame? onRenderFrame;
  final OnPreparedCompleted? onPreparedCompleted;
  final OnMixFrameRender? onMixFrameRender;
  final OnResourceClick? onResourceClick;

  /// 默认hybridComposition为true，部分需要支持截图的场景hybridComposition为false
  final bool hybridComposition;

  VapWidget(
      {Key? key,
      this.onPlatformViewCreated,
      this.onComplete,
      this.delayToShowFuture,
      this.onRenderFrame,
      this.hybridComposition = true,
      this.onResourceClick,
      this.onPreparedCompleted,
      this.onMixFrameRender})
      : super(key: key);

  @override
  _VapWidgetState createState() => _VapWidgetState();
}

class _VapWidgetState extends State<VapWidget> {
  double? _width;
  double? _height;
  FlutterVapController? _flutterVapController;

  @override
  Widget build(BuildContext context) {
    Widget? nativeView;
    if (defaultTargetPlatform == TargetPlatform.android) {
      final Map<String, dynamic> creationParams = <String, dynamic>{};
      String viewType = 'plugins.flutter.io/vap_view';

      if (widget.hybridComposition == false) {
        nativeView = AndroidView(
          viewType: viewType,
          layoutDirection: TextDirection.ltr,
          creationParams: creationParams,
          creationParamsCodec: const StandardMessageCodec(),
          gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
          onPlatformViewCreated: _onPlatformViewCreated,
        );
      } else {
        nativeView = PlatformViewLink(
          viewType: viewType,
          surfaceFactory: (
            BuildContext context,
            PlatformViewController controller,
          ) {
            return AndroidViewSurface(
              controller: controller as AndroidViewController,
              gestureRecognizers: const <
                  Factory<OneSequenceGestureRecognizer>>{},
              hitTestBehavior: PlatformViewHitTestBehavior.opaque,
            );
          },
          onCreatePlatformView: (PlatformViewCreationParams params) {
            return PlatformViewsService.initSurfaceAndroidView(
              id: params.id,
              viewType: viewType,
              layoutDirection: TextDirection.ltr,
              creationParams: creationParams,
              creationParamsCodec: const StandardMessageCodec(),
            )
              ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
              ..addOnPlatformViewCreatedListener(_onPlatformViewCreated)
              ..create();
          },
        );
      }
    } else if (defaultTargetPlatform == TargetPlatform.iOS) {
      nativeView = UiKitView(
        // key: widget.key,
        viewType: 'plugins.flutter.io/vap_view',
        onPlatformViewCreated: _onPlatformViewCreated,
      );
    }
    if (nativeView != null) {
      return LayoutBuilder(
          builder: (BuildContext context, BoxConstraints constraints) {
        _width = constraints.constrainWidth();
        _height = constraints.constrainHeight();
        return Container(
          width: _width,
          height: _height,
          // padding: EdgeInsetsDirectional.only(start: 100, end: 50, top: 50, bottom: 50),
          // color: Colors.red.withOpacity(0.5),
          child: nativeView,
        );
      });
    }
    return Text(
        '$defaultTargetPlatform platform version is not implemented yet.');
  }

  @override
  void dispose() {
    print('VapWidget:dispose');
    if (_flutterVapController != null) {
      _flutterVapController!.dispose();
    }
    super.dispose();
  }

  void _onPlatformViewCreated(int id) async {
    if (widget.onPlatformViewCreated == null) {
      return;
    }

    if (widget.delayToShowFuture != null) {
      await widget.delayToShowFuture;
    }

    print('_VapWidgetState._onPlatformViewCreated id = $id');
    _flutterVapController = FlutterVapController._(id,
        onResourceClick: widget.onResourceClick,
        onPreparedCompleted: widget.onPreparedCompleted,
        onComplete: widget.onComplete,
        onRenderFrame: widget.onRenderFrame,
        onMixFrameRender: widget.onMixFrameRender);
    _flutterVapController!.width = _width;
    _flutterVapController!.height = _height;
    widget.onPlatformViewCreated!(_flutterVapController!);
  }
}

class FlutterVapController {
  final VoidCallback? onComplete;
  final OnRenderFrame? onRenderFrame;
  final OnPreparedCompleted? onPreparedCompleted;
  final OnMixFrameRender? onMixFrameRender;
  final OnResourceClick? onResourceClick;

  VapConfig? _vapConfig;

  FlutterVapController._(int id,
      {this.onComplete,
      this.onRenderFrame,
      this.onPreparedCompleted,
      this.onResourceClick,
      this.onMixFrameRender})
      : _channel = MethodChannel('plugins/flutter_vap_$id') {
    _channel.setMethodCallHandler(_handlerMethod);
  }

  final MethodChannel _channel;

  double? width;
  double? height;

  /// 开始播放vap转制过的mp4视频文件
  ///
  /// [filePath]是完整的文件路径
  ///
  /// [repeatCount]是重复播放次数，值为-1时无限循环播放，为0时正常播放结束不再重复
  ///
  /// 融合信息：图片List
  /// [imgList]：vap支持嵌入图片,图片需要提前下载到本地，传本地路径
  ///
  /// 融合信息：文字List
  /// [textList]：vap支持嵌入文字
  Future<dynamic> startPlay(String filePath,
      {repeatCount = 0,
      List<String>? imgList,
      List<String>? textList,
      String? savePath}) async {
    if (repeatCount == -1 && Platform.isAndroid) {
      repeatCount = 100000;
    }

    String tempFilePath = filePath;
    if (filePath.startsWith('http://') || filePath.startsWith('https://')) {
      if (savePath == null) {
        String cachePath = await getGiftCachePath();
        String fileName = basename(filePath);
        File file = getGiftFile(fileName, cachePath);
        if (await file.exists()) {
          tempFilePath = file.path;
        } else {
          try {
            await download(filePath, file.path);
            tempFilePath = file.path;
          } catch (e) {
            return e;
          }
        }
      } else {
        File file = File(savePath);
        if (await file.exists()) {
          tempFilePath = savePath;
        } else {
          try {
            await download(filePath, savePath);
            tempFilePath = savePath;
          } catch (e) {
            return e;
          }
        }
      }
    } else {
      File file = File(filePath);
      if (await file.exists() == false) {
        return Exception('local file can not be null!!!');
      }
    }

    Map params = {
      'filePath': tempFilePath,
      'repeatCount': repeatCount,
      "imgList": imgList,
      "textList": textList
    };
    try {
      return await _channel.invokeMethod('startPlay', params);
    } catch (e) {
      print('FlutterVapController.startPlay error = ${e.toString()}');
      print(e);
      return e;
    }
  }

  Future<String> getGiftCachePath() async {
    Directory documentsDirectory = await getApplicationDocumentsDirectory();
    String baseDir = join(documentsDirectory.path, 'gift_big');
    await Directory(baseDir).create(recursive: true);
    return baseDir;
  }

  File getGiftFile(String fileName, String cachePath) {
    File file = File(join(cachePath, fileName));
    return file;
  }

  Future<Response> download(String url, String savePath) async {
    Dio dio = Dio();
    Response response = await dio.download(url, savePath);
    return response;
  }

  Future<bool> setMute(bool mute) async {
    try {
      return await _channel.invokeMethod('setMute', {'mute': mute});
    } catch (e) {
      print('FlutterVapController.setMute error = ${e.toString()}');
    }
    return false;
  }

  Future<bool> stopPlay() async {
    try {
      return await _channel.invokeMethod('stopPlay');
    } catch (e) {
      print('FlutterVapController.stopPlay error = ${e.toString()}');
    }
    return false;
  }

  Future<bool> pausePlay() async {
    try {
      return await _channel.invokeMethod('pausePlay');
    } catch (e) {
      print('FlutterVapController.stopPlay error = ${e.toString()}');
    }
    return false;
  }

  Future<bool> resumePlay() async {
    try {
      return await _channel.invokeMethod('resumePlay');
    } catch (e) {
      print('FlutterVapController.stopPlay error = ${e.toString()}');
    }
    return false;
  }

  Future<bool> setTextResource(String tag, String text) async {
    try {
      return await _channel
          .invokeMethod('setTextResource', {'tag': tag, 'text': text});
    } catch (e) {
      print('FlutterVapController.setTextResource error = ${e.toString()}');
    }
    return false;
  }

  Future<bool> setImageResource(String tag, String imgPath) async{
    try {
      return await _channel
          .invokeMethod('setImageResource', {'tag': tag, 'imgUrl': imgPath});
    } catch (e) {
      print('FlutterVapController.setImageResource error = ${e.toString()}');
    }
    return false;
  }

  void dispose() async {
    if (Platform.isIOS) {
      try {
        _channel.invokeMethod('dispose');
        print('FlutterVapController.dispose');
      } catch (e) {
        print('FlutterVapController.dispose error = ${e.toString()}');
        print(e);
      }
    }
  }

  VapConfig get vapConfig => _vapConfig ?? VapConfig(0, 0, 0, 0);

  Future<dynamic> _handlerMethod(MethodCall call) async {
    switch (call.method) {
      case 'onComplete':
        if (onComplete != null) {
          onComplete!();
        }
        break;
      case "onRenderFrame":
        Map params = call.arguments;
        int frameIndex = parseInt(params['frameIndex']);
        int frameTotal = parseInt(params['frameTotal']);
        if (onRenderFrame != null) {
          onRenderFrame!(frameIndex, frameTotal);
        }
        break;
      case "onPreparedCompleted":
        Map params = call.arguments;
        int frames = parseInt(params['frames']);
        int width = parseInt(params['width']);
        int height = parseInt(params['height']);
        int fps = parseInt(params['fps']);
        _vapConfig = VapConfig(frames, width, height, fps);
        if (onPreparedCompleted != null) {
          onPreparedCompleted!(_vapConfig!);
        }
        break;
      case 'onMixFrameRender':
        if (onMixFrameRender != null) {
          Map params = call.arguments;
          Map frame = params['frame'];
          MixFrameInfo info = MixFrameInfo(
              params['srcTag'],
              parseInt(params['index']),
              parseInt(frame['w']),
              parseInt(frame['h']),
              parseInt(frame['x']),
              parseInt(frame['y']));
          onMixFrameRender!(info);
        }
        break;
      case 'onResourceClick':
        dynamic data = call.arguments;
        print('onResourceClick === $data == ${data.runtimeType}');
        if (onResourceClick != null && data is String) {
          onResourceClick!(data);
        }
        break;
      default:
        break;
    }
  }
}

class VapConfig {
  final int frames;
  final int width;
  final int height;
  final int fps;

  VapConfig(this.frames, this.width, this.height, this.fps);

  @override
  String toString() {
    return 'frames=$frames,width=$width,height=$height,fps=$fps';
  }
}

class MixFrameInfo {
  final String srcTag;
  final int index;
  final int width;
  final int height;
  final int x;
  final int y;

  MixFrameInfo(
      this.srcTag, this.index, this.width, this.height, this.x, this.y);
}

int parseInt(dynamic value) {
  if (value is String) {
    return int.tryParse(value) ?? 0;
  }
  if (value is num) {
    return value.toInt();
  }

  if (value is bool) {
    return value ? 1 : 0;
  }
  return 0;
}
